{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "import os\n",
    "import asyncio\n",
    "\n",
    "from dotenv import load_dotenv\n",
    "\n",
    "from typing import Annotated\n",
    "from openai import AsyncOpenAI\n",
    "\n",
    "\n",
    "from semantic_kernel.kernel import Kernel\n",
    "from semantic_kernel.connectors.ai.open_ai import OpenAIChatCompletion\n",
    "from semantic_kernel.agents import ChatCompletionAgent\n",
    "from semantic_kernel.contents import ChatHistory\n",
    "\n",
    "\n",
    "from semantic_kernel.agents.open_ai import OpenAIAssistantAgent\n",
    "from semantic_kernel.contents import AuthorRole, ChatMessageContent\n",
    "from semantic_kernel.functions import kernel_function\n",
    "\n",
    "from semantic_kernel.connectors.ai import FunctionChoiceBehavior\n",
    "\n",
    "from semantic_kernel.contents.function_call_content import FunctionCallContent\n",
    "from semantic_kernel.contents.function_result_content import FunctionResultContent\n",
    "from semantic_kernel.functions import KernelArguments, kernel_function"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Define a sample plugin for the sample\n",
    "class DestinationsPlugin:\n",
    "    \"\"\"A List of Destinations for vacation.\"\"\"\n",
    "\n",
    "    @kernel_function(description=\"Provides a list of vacation destinations.\")\n",
    "    def get_destinations(self) -> Annotated[str, \"Returns the specials from the menu.\"]:\n",
    "        return \"\"\"\n",
    "        Barcelona, Spain\n",
    "        Paris, France\n",
    "        Berlin, Germany\n",
    "        Tokyo, Japan\n",
    "        New York, USA\n",
    "        \"\"\"\n",
    "\n",
    "    @kernel_function(description=\"Provides available flight times for a destination.\")\n",
    "    def get_flight_times(\n",
    "        self, destination: Annotated[str, \"The destination to check flight times for.\"]\n",
    "    ) -> Annotated[str, \"Returns flight times for the specified destination.\"]:\n",
    "        # Return HTTP ERROR 404\n",
    "        return \"HTTP ERROR 404: Flight times service is currently unavailable.\"\n",
    "\n",
    "    @kernel_function(description=\"Backup function that provides available flight times for a destination.\")\n",
    "    def get_flight_times_backup(\n",
    "        self, destination: Annotated[str, \"The destination to check flight times for.\"]\n",
    "    ) -> Annotated[str, \"Returns flight times for the specified destination.\"]:\n",
    "        flight_times = {\n",
    "            \"Barcelona\": [\"08:30 AM\", \"02:15 PM\", \"10:45 PM\"],\n",
    "            \"Paris\": [\"06:45 AM\", \"12:30 PM\", \"07:15 PM\"],\n",
    "            \"Berlin\": [\"07:20 AM\", \"01:45 PM\", \"09:30 PM\"],\n",
    "            \"Tokyo\": [\"11:00 AM\", \"05:30 PM\", \"11:55 PM\"],\n",
    "            \"New York\": [\"05:15 AM\", \"03:00 PM\", \"08:45 PM\"]\n",
    "        }\n",
    "\n",
    "        # Extract just the city name from input that might contain country\n",
    "        city = destination.split(',')[0].strip()\n",
    "\n",
    "        if city in flight_times:\n",
    "            times = \", \".join(flight_times[city])\n",
    "            return f\"Flight times for {city}: {times}\"\n",
    "        else:\n",
    "            return f\"No flight information available for {city}.\""
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "load_dotenv()\n",
    "client = AsyncOpenAI(\n",
    "    api_key=os.getenv(\"GITHUB_TOKEN\"), base_url=\"https://models.inference.ai.azure.com/\")\n",
    "\n",
    "kernel = Kernel()\n",
    "kernel.add_plugin(DestinationsPlugin(), plugin_name=\"destinations\")\n",
    "\n",
    "service_id = \"agent\"\n",
    "\n",
    "chat_completion_service = OpenAIChatCompletion(\n",
    "    ai_model_id=\"gpt-4o-mini\",\n",
    "    async_client=client,\n",
    "    service_id=service_id\n",
    ")\n",
    "kernel.add_service(chat_completion_service)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "settings = kernel.get_prompt_execution_settings_from_service_id(\n",
    "    service_id=service_id)\n",
    "settings.function_choice_behavior = FunctionChoiceBehavior.Auto()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [],
   "source": [
    "AGENT_NAME = \"TravelAgent\"\n",
    "AGENT_INSTRUCTIONS = \"\"\" \\\n",
    "\"You are Flight Booking Agent that provides information about available flights and gives travel activity suggestions when asked.\n",
    "Travel activity suggestions should be specific to customer, location and amount of time at location.\n",
    "\n",
    "You have access to the following tools to help users plan their trips:\n",
    "1. get_destinations: Returns a list of available vacation destinations that users can choose from.\n",
    "2. get_flight_times: Provides available flight times for specific destinations.\n",
    "3. get_flight_times_backup: Backup function that provides available flight times when the primary service is down.\n",
    "\n",
    "Your process for assisting users:\n",
    "- When users inquire about flight booking, book the earliest flight available for the destination they choose using get_flight_times.\n",
    "- If get_flight_times returns an error message, immediately use get_flight_times_backup with the same destination parameter to retrieve flight information.\n",
    "- Since you do not have access to a booking system, DO NOT ask to proceed with booking, just assume you have booked the flight.\n",
    "- Use any past conversation history to understand user preferences and consider them when making suggestions on flights and activities. When making a suggestion, be very clear on why you are making this suggestion if based on a user preference.\n",
    "\n",
    "Guidelines:\n",
    "- Use the exact destination names when using tools (Barcelona, Paris, Berlin, Tokyo, New York)\n",
    "- Respond in a helpful and enthusiastic manner about travel possibilities\n",
    "- Always seek feedback to ensure your suggestions meet the user's expectations\n",
    "- Acknowledge when a request falls outside your capabilities\n",
    "- For better formatting, always display flight times in a list format\n",
    "- When giving any timed suggestions, reflect if the time frames are reasonable. Respond again if not.\n",
    "- If the flight times service is down, inform the user that you're using backup flight data while maintaining a positive tone.\n",
    "\n",
    "Your goal is to help users explore vacation options efficiently and make informed travel decisions by understanding their preferences and providing tailored recommendations.\n",
    "\"\"\"\n",
    "# Create the agent\n",
    "agent = ChatCompletionAgent(\n",
    "    service_id=service_id,\n",
    "    kernel=kernel,\n",
    "    name=AGENT_NAME,\n",
    "    instructions=AGENT_INSTRUCTIONS,\n",
    "    arguments=KernelArguments(settings=settings)\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div style='margin-bottom:10px'><div style='font-weight:bold'>User:</div><div style='margin-left:20px'>Book me a flight to Barcelona</div></div><div style='margin-bottom:10px'><details><summary style='cursor:pointer; font-weight:bold; color:#0066cc;'>Function Calls (click to expand)</summary><div style='margin:10px; padding:10px; background-color:#f8f8f8; border:1px solid #ddd; border-radius:4px; white-space:pre-wrap;'>Calling: get_flight_times()<br>Calling: ({\")<br>Calling: (destination)<br>Calling: (\":\")<br>Calling: (Barcelona)<br>Calling: (\"})<br>Result: HTTP ERROR 404: Flight times service is currently unavailable.<br>Calling: get_flight_times_backup()<br>Calling: ({\")<br>Calling: (destination)<br>Calling: (\":\")<br>Calling: (Barcelona)<br>Calling: (\"})<br>Result: Flight times for Barcelona: 08:30 AM, 02:15 PM, 10:45 PM</div></details></div><div style='margin-bottom:20px'><div style='font-weight:bold'>TravelAgent:</div><div style='margin-left:20px; white-space:pre-wrap'>I've found some available flight times to Barcelona! Here they are:\n",
       "\n",
       "-08:30 AM-02:15 PM-10:45 PMI've booked you on the earliest flight available at08:30 AM. Enjoy your trip to the beautiful city of Barcelona!\n",
       "\n",
       "If you need any suggestions for activities while you're there or anything else, just let me know!</div></div><hr>"
      ],
      "text/plain": [
       "<IPython.core.display.HTML object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "from IPython.display import display, HTML\n",
    "\n",
    "# Define the chat history outside of the function\n",
    "chat_history = ChatHistory()\n",
    "\n",
    "\n",
    "async def main():\n",
    "    # Respond to user input\n",
    "    user_inputs = [\n",
    "        \"Book me a flight to Barcelona\",\n",
    "    ]\n",
    "\n",
    "    for user_input in user_inputs:\n",
    "        # Add the user input to the chat history\n",
    "        chat_history.add_user_message(user_input)\n",
    "\n",
    "        # Start building HTML output\n",
    "        html_output = f\"<div style='margin-bottom:10px'>\"\n",
    "        html_output += f\"<div style='font-weight:bold'>User:</div>\"\n",
    "        html_output += f\"<div style='margin-left:20px'>{user_input}</div>\"\n",
    "        html_output += f\"</div>\"\n",
    "\n",
    "        agent_name: str | None = None\n",
    "        full_response = \"\"\n",
    "        function_calls = []\n",
    "\n",
    "        # Collect the agent's response with function call tracking\n",
    "        async for content in agent.invoke_stream(chat_history):\n",
    "            if not agent_name and hasattr(content, 'name'):\n",
    "                agent_name = content.name\n",
    "\n",
    "            # Track function calls and results\n",
    "            for item in content.items:\n",
    "                if isinstance(item, FunctionCallContent):\n",
    "                    call_info = f\"Calling: {item.function_name}({item.arguments})\"\n",
    "                    function_calls.append(call_info)\n",
    "                elif isinstance(item, FunctionResultContent):\n",
    "                    result_info = f\"Result: {item.result}\"\n",
    "                    function_calls.append(result_info)\n",
    "\n",
    "            # Add content to response if it's not a function-related message\n",
    "            if (hasattr(content, 'content') and\n",
    "                content.content and\n",
    "                content.content.strip() and\n",
    "                not any(isinstance(item, (FunctionCallContent, FunctionResultContent))\n",
    "                        for item in content.items)):\n",
    "                full_response += content.content\n",
    "\n",
    "        # Add function calls to HTML if any occurred\n",
    "        if function_calls:\n",
    "            html_output += f\"<div style='margin-bottom:10px'>\"\n",
    "            html_output += f\"<details>\"\n",
    "            html_output += f\"<summary style='cursor:pointer; font-weight:bold; color:#0066cc;'>Function Calls (click to expand)</summary>\"\n",
    "            html_output += f\"<div style='margin:10px; padding:10px; background-color:#f8f8f8; border:1px solid #ddd; border-radius:4px; white-space:pre-wrap;'>\"\n",
    "            html_output += \"<br>\".join(function_calls)\n",
    "            html_output += f\"</div></details></div>\"\n",
    "\n",
    "        # Add agent response to HTML\n",
    "        html_output += f\"<div style='margin-bottom:20px'>\"\n",
    "        html_output += f\"<div style='font-weight:bold'>{agent_name or 'Assistant'}:</div>\"\n",
    "        html_output += f\"<div style='margin-left:20px; white-space:pre-wrap'>{full_response}</div>\"\n",
    "        html_output += f\"</div>\"\n",
    "        html_output += \"<hr>\"\n",
    "\n",
    "        # Display formatted HTML\n",
    "        display(HTML(html_output))\n",
    "\n",
    "        # Add the assistant's response to the chat history\n",
    "        chat_history.add_assistant_message(full_response)\n",
    "\n",
    "\n",
    "await main()"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": ".venv",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.13.1"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
